package com.atguigu.userprofile.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.userprofile.bean.TagCondition;
import com.atguigu.userprofile.bean.TagInfo;
import com.atguigu.userprofile.bean.UserGroup;
import com.atguigu.userprofile.constants.ConstCodes;
import com.atguigu.userprofile.mapper.UserGroupMapper;
import com.atguigu.userprofile.service.TagInfoService;
import com.atguigu.userprofile.service.UserGroupService;
import com.atguigu.userprofile.utils.RedisUtil;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;

import org.apache.catalina.User;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author zhangchen
 * @since 2021-05-04
 */
@Service
@Slf4j
@DS("mysql")
public class UserGroupServiceImpl extends ServiceImpl<UserGroupMapper, UserGroup> implements UserGroupService {

    @Autowired
    TagInfoService tagInfoService;


    @Override
    public void saveUserGroupInfo(UserGroup userGroup) {
        //1 把不能保存在数据库中的list转为json
        List<TagCondition> tagConditions = userGroup.getTagConditions();
        String conditionJson = JSON.toJSONString(tagConditions);
        userGroup.setConditionJsonStr(conditionJson);
        //2 提前条件中的中文，组合一个备注字段
        String comment = userGroup.conditionJsonToComment();
        userGroup.setConditionComment(comment);
        //3 补充创建时间
        userGroup.setCreateTime(new Date());
        //baseMapper.insert(userGroup);
        super.saveOrUpdate(userGroup);

    }


    //组合出如下的sql  ，提交执行
    // insert into  user_group
    //select  bitmapAnd(
    //  bitmapAnd(
    //     (select   groupBitmapMergeState(us)  from user_tag_value_decimal where   tag_code='tg_behavoir_order_amount_7d' and tag_value> 0 and dt='2020-06-14')
    //     ,
    //     (select  groupBitmapMergeState(us)  from user_tag_value_string where tag_code='tg_person_base_agegroup' and tag_value in('80后','90后','00后') and dt='2020-06-14')
    //   )
    //  ,
    //  (select groupBitmapMergeState(us) from user_tag_value_string where tag_code='tg_person_base_gender' and tag_value='男' and dt='2020-06-14')
    // ));
    @Override
    public void genUserGroup(UserGroup userGroup) {


        //   调用拼接insert sql的方法
        String insertSQL = insertSQL(userGroup);
        // 幂等性处理
        baseMapper.deleteUserGroup(userGroup.getId()+"");

        //   执行sql
        baseMapper.insertUserGroupBitmap(insertSQL);

    }

    @Override
    public Long saveUserGroupRedis(UserGroup userGroup) {
        Jedis jedis = RedisUtil.getJedis();
        //type?     set  判存方便  全取也方便  幂等
        //  key ?   user_group:[user_group_id]    value?  uids       score/field? 无
        // 写入 api  sadd      读取 api ?  sismember 判存   smembers 全取
        // 过期？ 不过期


        //1 读取已经存储的clickhouse 分群结果
   //     List<String> uidList = baseMapper.getBitmapArray(userGroup.getId()+"");  //不再依赖clickhouse的计算结果
        List<String> uidList = getBitmapArrayBySQL(userGroup);

        //2 写入redis

        String[] uids = uidList.toArray(new String[]{});
        String key="user_group:"+userGroup.getId();

        jedis.del(key);

        jedis.sadd(key,uids);


        jedis.close();

        return uids.length+0L;
    }

    //把bitmapAnd 拼接成insert sql
    public String insertSQL(UserGroup userGroup){
        String bitmapSQL = bitmapAndSQL(userGroup.getTagConditions(),userGroup.getBusiDate());
        String insertSQL="insert into  user_group select   '"+userGroup.getId()+"' ,"+bitmapSQL;
        return insertSQL;
    }

    public  Long  cardinalityUserGroup(UserGroup userGroup){
        String bitmapSQL = bitmapAndSQL(userGroup.getTagConditions(),userGroup.getBusiDate());
        String cardinalitySQL=" select bitmapCardinality("+bitmapSQL+")";
        Long num = baseMapper.cardinalityUserGroup(cardinalitySQL);

        return num;
    }

    /**
     * 根据sql直接查询出bitmap的用户列表
     * @param userGroup
     * @return
     */
    public  List<String>  getBitmapArrayBySQL(UserGroup userGroup){
        String bitmapSQL = bitmapAndSQL(userGroup.getTagConditions(),userGroup.getBusiDate());
        String bitmapArraySQL=" select  arrayJoin( bitmapToArray("+bitmapSQL+") )";
        List<String> uidList = baseMapper.getBitmapArrayBySQL(bitmapArraySQL);
        return uidList;
    }


    //  把多条单条子查询拼接成bitmapAnd sql
    // bitmapAnd(
    //       bitmapAnd(
    //          (select 1 )
    //         ,
    //          (select 2 )
    //        )
    //      ,
    //       (select 3 )
    //     ))
    public  String bitmapAndSQL(List<TagCondition> tagConditionList ,String busiDate){
        //循环调用  getConditionSQL
        Map<String ,TagInfo> tagInfoMap = tagInfoService.getTagInfoMapWithCode();

        StringBuilder sqlBuilder=new StringBuilder();
        for (TagCondition tagCondition : tagConditionList) {
            String conditionSQL = getConditionSQL(tagCondition, tagInfoMap, busiDate);

            if(sqlBuilder.length()==0){
                sqlBuilder.append(conditionSQL);
            }else{
                // 一个以上要加bitmapAnd
                sqlBuilder.insert(0,"bitmapAnd(").append(",").append(conditionSQL).append(")");

            }
        }

        return sqlBuilder.toString();
    }

    // 生成单条条件的子查询
    //(select groupBitmapMergeState(us)  from user_tag_value_string where tag_code='tg_person_base_gender' and tag_value='男' and dt='2020-06-14')
    public  String getConditionSQL(TagCondition tagCondition,Map<String ,TagInfo> tagInfoMap,String busiDate){
        //表名： 要根据tag_info里tag_value_type字段进行判断 得到标签值类型
        //  直接从tagInfoMap中查询tagInfo
        //tag_code  => tagCondition.tagCode
        //operator => tagCondition.operator里获得英文，转义成符号
        //tag_value =>  1  1 个还是  多个    小括号 ，用逗号拼接
        //            = >     字符串还是数字   是否加单引   根据tag_value_type 进行判断
        // dt => busiDate

        String tagCode=tagCondition.getTagCode();

        TagInfo tagInfo = tagInfoMap.get(tagCode);
        String tagValueType = tagInfo.getTagValueType();

        String tableName=null;
        if(tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_STRING)){
            tableName="user_tag_value_string";
        }else if(tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_LONG)){
            tableName="user_tag_value_long";
        }else if(tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_DECIMAL)){
            tableName="user_tag_value_decimal";
        }else if(tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_DATE)){
            tableName="user_tag_value_date";
        }

        String operatorEng = tagCondition.getOperator();
        String conditionOperator = getConditionOperator(operatorEng);

        List<String> tagValueList = tagCondition.getTagValues();

        String tagValueSQL="";
        if(tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_LONG)||tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_DECIMAL)){
            tagValueSQL=StringUtils.join(tagValueList,",");  // 数字 不加单引
        }else{
            tagValueSQL=  "'"+ StringUtils.join(tagValueList,"','")+"'";// 非数字  加单引
        }
        if(tagValueList.size()>1){
            tagValueSQL="("+tagValueSQL+")";
        }

        String conditionSQL="(select groupBitmapMergeState(us)  from " +
                tableName +
                " where tag_code='"+tagCode.toLowerCase()+"' " +
                "and tag_value "+ conditionOperator+"  " +tagValueSQL+
                " and dt='"+busiDate+"') ";


        return  conditionSQL;
    }


    private  String getConditionOperator(String operator){
        switch (operator){
            case "eq":
                return "=";
            case "lte":
                return "<=";
            case "gte":
                return ">=";
            case "lt":
                return "<";
            case "gt":
                return ">";
            case "neq":
                return "<>";
            case "in":
                return "in";
            case "nin":
                return "not in";
        }
        throw  new RuntimeException("操作符不正确");
    }

}
